﻿namespace WingBeats.Mvc

open System
open System.Reflection
open System.Collections.Concurrent
open WingBeats.Xml

type private ViewMethod = { Type:Type; Method:MethodInfo }

type WingBeatsTemplateEngine() =
    static let config = WingBeatsConfigurationSettings.GetSection()
    // TODO: discard cache when assembly changes!
    static let assemblies = ConcurrentDictionary<string, Assembly>()
    static let methods = ConcurrentDictionary<(string * string * string), ViewMethod>()

    let assemblySource = config.ViewAssembly

    interface System.Web.Mvc.IViewEngine with

        member this.FindPartialView (context, str, boolean) =
            raise <| System.NotImplementedException()

        member this.FindView (controllerContext, viewName, masterName, useCache) =
            let controllerName = controllerContext.RouteData.Values.["controller"] :?> string

            let getAssembly assemblyName =
                assemblies.GetOrAdd(assemblyName, (fun an -> Assembly.Load(AssemblyName(an))))

            let getMethod (assemblyName, controllerName, viewName) =
                methods.GetOrAdd((assemblyName, controllerName, viewName), 
                    (fun (a,c,v) -> 
                        let theType = (getAssembly a).GetType(sprintf "%s.%s" a c)
                        { Type=theType; Method=theType.GetMethod(v) }))

            let getNode (assemblyName, viewContext, viewName) : Node =
                let vm = getMethod (assemblyName, controllerName, viewName)
                // TODO: parameter selection is pretty brain-dead right now...
                match vm.Method.GetParameters().Length with
                | 0 -> vm.Method.Invoke(null, null) :?> Node
                | 1 -> vm.Method.Invoke(null, [|controllerContext.Controller.ViewData.Model|]) :?> Node
                | _ -> vm.Method.Invoke(null, [|controllerContext.Controller.ViewData.Model; viewContext|]) :?> Node
            
            match assemblySource with
            | "" | null -> failwith "Must specify an assemblySource in the web.config."
            | _  ->
                let theView = { new System.Web.Mvc.IView with
                                member this.Render(viewContext, textWriter) =
                                    let theNode = getNode(assemblySource, viewContext, viewName)
                                    Render.renderNodeText textWriter theNode }
                System.Web.Mvc.ViewEngineResult(theView, this)

        member this.ReleaseView (context, view) = ()
      